package ar.uba.fi.celdas.bc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import planificador.Planificador;

import ar.fi.uba.celdas.correccion.planificacion.CorrectorPlanificacion;
import ar.fi.uba.celdas.correccion.planificacion.HeuristicaPlanificacionType;
import ar.fi.uba.celdas.correccion.prediccion.CorrectorPrediccion;
import ar.fi.uba.celdas.interfaces.ambiente.IAccion;
import ar.fi.uba.celdas.interfaces.ambiente.IInfoRobot;
import ar.fi.uba.celdas.interfaces.bc.IBaseConocimientos;
import ar.fi.uba.celdas.interfaces.bc.Regla;
import ar.fi.uba.celdas.interfaces.bc.Sensor;
import ar.fi.uba.celdas.interfaces.planificacion.IPlanificacion;
import ar.fi.uba.celdas.interfaces.ponderador.IPonderador;
import ar.uba.fi.celdas.ponderador.Ponderador;

public class BaseConocimientos implements IBaseConocimientos {

	private static final Log log = LogFactory.getLog(BaseConocimientos.class);

	private List<Regla> baseConocimiento = new LinkedList<Regla>();
	private Collection<Sensor> objetivo;
	
	private Collection<Sensor> estadoAnterior = null;
	private Collection<Sensor> estadoActual = null;
	
	private Regla reglaLocal = null;

	private Regla ultimaReglaEjecutada = null;

	private IPlanificacion planificador = new Planificador();
	private IPonderador ponderador = new Ponderador();

	private List<Regla> reglasPlan = null;
	private int indiceReglaPlanEjecutada = 0;
	
	IInfoRobot infoRobot = null;

	@Override
	public List<Regla> getReglas() {

		return baseConocimiento;
	}

	@Override
	public Collection<Sensor> getObjetivo() {

		return objetivo;
	}

	@Override
	public Collection<Sensor> getEstadoAnterior() {

		return estadoAnterior;
	}

	@Override
	public Collection<Sensor> getEstadoActual() {

		return estadoActual;
	}

	@Override
	public Collection<IAccion> getAccionesDisponibles() {

		List<Integer> valores = new ArrayList<Integer>();
		Regla regla = new Regla();
		Iterator<Regla> it = baseConocimiento.iterator();
		regla = it.next();

		while (it.hasNext()) {

			if (it.next().getUtilidad() > regla.getUtilidad()) {

				regla = it.next();
			}
		}
		return regla.getAcciones();
	}
	
	public static Collection<Sensor> cloneList(Collection<Sensor> list) {
	    Collection<Sensor> clone = new ArrayList<Sensor>(list.size());
	    for(Sensor item: list) clone.add(item.clone());
	    return clone;
	}

	@Override
	public List<IAccion> procesarSiguientesAcciones(IInfoRobot info) {
		
		this.infoRobot = info;
		this.reglaLocal = null;

		// Actualiza los estados
		actualizarEstados();
		
		int correccion = correccionReglaEjecutada();
		
		if (correccion != CORRECCION_PLANIFICACION) {
			//Crea una regla local
			crearReglaLocal();
			
			//Refuerza Reglas iguales a reglaLocal o agrega reglaLocal a baseConocimiento
			reforzarReglas();
		}
		
		

		if (reglasPlan == null || reglasPlan.isEmpty() || indiceReglaPlanEjecutada >= reglasPlan.size() || correccion != SIN_CORRECCION) {

			// No hay plan de acción, hay que crear uno usando el planificador
			// Habiendo incorporado antes la regla local para que pueda
			// planificar
			log.trace("Generar Plan de Accion");
			if (baseConocimiento.isEmpty()) {
				log.trace("No hay reglas en la base, generando nueva regla");
				Regla regla = crearReglaConAccionesPosibles();
				baseConocimiento.add(regla);
			}
			
			log.trace("Llamando al planificador");
			if (!baseConocimiento.isEmpty()) {
				try {
					reglasPlan = planificador.planificar(this);
				} catch (Exception e) {
					log.trace("Error al planificar");
				}
			}
			indiceReglaPlanEjecutada = 0;
			log.debug("Reglas del planificador: " + reglasPlan);
		}

		Regla reglaAEjecutar = null;
		if (reglasPlan == null || reglasPlan.isEmpty()) {
			log.trace("No se genero un plan, buscando regla que coincida con el estado actual");
			if (correccion == SIN_CORRECCION) {
				reglaAEjecutar = encontrarMejorReglaAEjecutar();
			}
			if (reglaAEjecutar == null ){
				log.trace("-- No hay regla que coincida, generando una nueva regla con acciones posibles");
				reglaAEjecutar = crearReglaConAccionesPosibles();
				baseConocimiento.add(reglaAEjecutar);
			}
		} else {
			reglaAEjecutar = reglasPlan.get(indiceReglaPlanEjecutada++);
		}
		
		ultimaReglaEjecutada = reglaAEjecutar;

		return reglaAEjecutar.getAcciones();
	}
	
	
	private void actualizarEstados() {
		log.trace("Actualizando estados...");
		if (estadoActual == null) {
			estadoAnterior = null;
		} else {
			estadoAnterior = cloneList(estadoActual);
		}
		estadoActual = cloneList(infoRobot.getEstado());
		log.debug("Estado anterior: " + estadoAnterior);
		log.debug("Estado actual: " + estadoActual);
	}
	
	private void reforzarReglas() {
		if (reglaLocal != null) {
			boolean reglaLocalEncontrada = false;
			for (Regla regla : baseConocimiento) {
				if (regla.matchCondicionesAccionesPredicciones(reglaLocal)) {
					regla.registrarUso();
					regla.registrarUsoExitoso();
					reglaLocalEncontrada = true;
				}
			}
			if (!reglaLocalEncontrada) {
				baseConocimiento.add(reglaLocal);
			}
		}
	}
	
	private final int SIN_CORRECCION			= 0;
	private final int CORRECCION_PLANIFICACION 	= 1;
	private final int CORRECCION_PREDICCION 	= 2;
	
	private int correccionReglaEjecutada() {
		if (ultimaReglaEjecutada != null) {
			if (!infoRobot.ultimasAccionesExitosas()) {
				//Si la accion no se pudo realizar corregimos planificacion
				log.trace("El robot no pudo ejecutar exitosamente las acciones");
				CorrectorPlanificacion.getCorrector(HeuristicaPlanificacionType.RETRACCION).corregirRegla(ultimaReglaEjecutada, this);
				log.trace("Se corrigio la regla ejecutada usando heuristicas de planificacion");
				ultimaReglaEjecutada.registrarUso();
				return CORRECCION_PLANIFICACION;
			} else if (!ultimaReglaEjecutada.matchPredicciones(estadoActual)) {
				//Si el estado predecido no se cumple corregimos prediccion
				log.trace("El estado predecido por la ultima regla ejecutada no es el estado actual");
				CorrectorPrediccion.getCorrector().corregirRegla(ultimaReglaEjecutada, this);
				log.trace("Se corrigio la regla ejecutada usando heuristicas de prediccion");
				ultimaReglaEjecutada.registrarUso();
				return CORRECCION_PREDICCION;
			}
			log.trace("La regla ejecutada fue exitosa.");
		}
		return SIN_CORRECCION;
	}
	
	private Regla crearReglaConAccionesPosibles() {
		Random rnd = new Random();
		int sizeAcciones = infoRobot.getAccionesPosibles().size();
		List<IAccion> accionesAEjecutar = (List<IAccion>) ((List<Collection<IAccion>>) infoRobot.getAccionesPosibles()).get(rnd.nextInt(sizeAcciones));
		
		Regla regla = crearRegla(estadoActual, estadoActual, accionesAEjecutar);
		
		int posX = ((ArrayList<Sensor>) infoRobot.getPosicionActual()).get(0).getValor();
		int posY = ((ArrayList<Sensor>) infoRobot.getPosicionActual()).get(1).getValor();
		int objX = ((ArrayList<Sensor>) infoRobot.getObjetivo()).get(0).getValor();
		int objY = ((ArrayList<Sensor>) infoRobot.getObjetivo()).get(1).getValor();
		int distX = posX - objX;
		int distY = posY - objY;
		double dist = Math.sqrt(Math.pow(distX, 2) + Math.pow(distY, 2));
		int utilidad = (int)(1000 * (1/dist));
		regla.setUtilidad(utilidad);
		return crearRegla(estadoActual, estadoActual, accionesAEjecutar);
	}
	
	private void crearReglaLocal() {
		log.trace("Armando regla local");
		if (estadoAnterior != null) {
			reglaLocal = crearRegla(estadoAnterior, estadoActual, ultimaReglaEjecutada.getAcciones());
		}
		log.debug("ReglaLocal = " + reglaLocal);
	}

	private Regla crearRegla(Collection<Sensor> estadoAnterior, Collection<Sensor> estadoActual, List<IAccion> accionesEjecutadas) {

		Regla regla = new Regla();
		
		Collection<Sensor> condiciones = regla.getCondiciones();
		condiciones.addAll(getSensoresConValor(estadoAnterior));
		
		List<IAccion> acciones = regla.getAcciones();
		acciones.addAll(accionesEjecutadas);
		
		Collection<Sensor> predicciones = regla.getPredicciones();
		predicciones.addAll(cloneList(estadoActual));
		
		regla.setExitos(1);
		regla.setUso(1);

		return regla;
	}

	private Collection<Sensor> getSensoresConValor(Collection<Sensor> estadoAnterior) {
		Collection<Sensor> sensores = new ArrayList<Sensor>();
		for (Sensor sensor : estadoAnterior) {
			if (sensor.getValor() < getMaxValor(sensor)) {
				sensores.add(sensor.clone());
			}
		}
		if (sensores.isEmpty()) {
			sensores = cloneList(estadoAnterior);
		}
		return sensores;
	}
	
	private Regla encontrarMejorReglaAEjecutar() {
		Regla mejorRegla = null;
		for (Regla regla : baseConocimiento) {
			if (regla.matchCondiciones(estadoActual)) {
				if (mejorRegla != null) {
					if (mejorRegla.compareTo(regla) > 0) {
						mejorRegla = regla;
					}
				} else {
					mejorRegla = regla;
				}
			}
		}
		return mejorRegla;
	}

	@Override
	public int getMaxValor(Sensor sensor) {
		if (infoRobot != null) {
			return infoRobot.getMaxValor(sensor);
		}
		return 0;
	}

	@Override
	public int getMinValor(Sensor sensor) {
		if (infoRobot != null) {
			return infoRobot.getMinValor(sensor);
		}
		return 0;
	}

	@Override
	public Collection<Sensor> getEstadosConocidos() {
		return null;
	}

	public void setPlanificador(IPlanificacion planificador) {

		this.planificador = planificador;
	}

	public void setPonderador(IPonderador ponderador) {

		this.ponderador = ponderador;
	}
}
